package cz.semjob.output;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import org.apache.log4j.Logger;

import com.hp.hpl.jena.ontology.Individual;
import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec;
import com.hp.hpl.jena.ontology.OntProperty;
import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.ResourceFactory;
import com.hp.hpl.jena.util.FileManager;
import com.hp.hpl.jena.util.iterator.ExtendedIterator;

import cz.semjob.output.data.CandidateConcept;
import cz.semjob.output.data.CandidateInstance;
import cz.semjob.output.data.EnrichmentOutput;


public class RDFExport {
	
	private String ontologyPath;
	private String prefix;
	private String base;
	private static Logger logger = Logger.getLogger(RDFExport.class);
	
	public RDFExport() {
		// TODO Auto-generated constructor stub
	}
	
	public OntModel createModel(EnrichmentOutput enrichmentOutput, String ontologyPath) {
		if (ontologyPath == null) {
			ontologyPath = this.ontologyPath;
		}
		OntModel model = ModelFactory.createOntologyModel();
		OntModel ontology = readModel(ontologyPath);
		Map<CandidateConcept, OntClass> conceptClassMap = addConceptsToModel(enrichmentOutput, model, ontology);
		addInstancesToModel(enrichmentOutput, model, ontology, conceptClassMap);
		return model;
	}

	private void addInstancesToModel(EnrichmentOutput enrichmentOutput,
			OntModel model, OntModel ontology, Map<CandidateConcept, OntClass> conceptClassMap) {
		for(CandidateInstance candidateInstance : enrichmentOutput.getCandidateInstances()) {
			// TODO Activity nebude na pevno
			String conceptName = candidateInstance.getLinkedConcept().getEntity().getName();
			if (conceptName.equals("Activity")) {
				Individual documentIndividual = null;
				String documentUri = base + candidateInstance.getDocumentInstance().getFileName();
				if (model.getIndividual(base + candidateInstance.getDocumentInstance().getFileName()) != null) {
					documentIndividual = model.getIndividual(documentUri);
				} else {
					OntClass documentClass = ontology.getResource(base+"JD").as(OntClass.class);
					documentIndividual = model.createIndividual(documentUri, documentClass);
				}
				Individual instanceIndividual = createIndividual(ontology, model, candidateInstance, conceptClassMap);
				Property hasConceptProperty = model.getProperty(base+"has"+conceptName);
				documentIndividual.addProperty(hasConceptProperty, instanceIndividual);
				
			}
		}
	}
	
	private Individual createIndividual(OntModel ontology, OntModel model, CandidateInstance candidateInstance, Map<CandidateConcept, OntClass> conceptClassMap) {
		String conceptName = candidateInstance.getLinkedConcept().getNormalizedRepresentation();
		OntClass conceptClass = conceptClassMap.get(candidateInstance.getLinkedConcept());
		Individual instanceIndividual = model.createIndividual(base+conceptName+"_"+UUID.randomUUID(), conceptClass);
		Property hasStartIndexProperty = model.getProperty(base+"hasStartIndex");
		Property hasEndIndexProperty = model.getProperty(base+"hasEndIndex");
		Property hasTextProperty = model.getProperty(base+"hasText");
		Property belongsToDocument = model.getProperty(base+"belongsToDocument");
		instanceIndividual.addProperty(hasStartIndexProperty, ResourceFactory.createTypedLiteral(candidateInstance.getStartIndex()));
		instanceIndividual.addProperty(hasEndIndexProperty, ResourceFactory.createTypedLiteral(candidateInstance.getEndIndex()));
		instanceIndividual.addProperty(hasTextProperty, ResourceFactory.createTypedLiteral(candidateInstance.getText()));
		instanceIndividual.addProperty(belongsToDocument, ResourceFactory.createTypedLiteral(candidateInstance.getDocumentInstance().getFileName()));
		
		for(String relation : candidateInstance.getRelations().keySet()) {
			CandidateInstance relatedInstance = candidateInstance.getRelations().get(relation);
			Property hasProperty = model.getProperty(base+"has"+relation);
			if (hasProperty != null) {
				Individual relatedIndividual = createIndividual(ontology, model, relatedInstance, conceptClassMap);
				instanceIndividual.addProperty(hasProperty, relatedIndividual);
			}
		}
		return instanceIndividual;
		
	}

	private Map<CandidateConcept, OntClass> addConceptsToModel(EnrichmentOutput enrichmentOutput,
			OntModel model, OntModel ontology) {
		Map<CandidateConcept, OntClass> conceptRootClassMap = new HashMap<CandidateConcept, OntClass>();
		for(CandidateConcept concept: enrichmentOutput.getCandidateConcepts()) {
			addRootCandidateConceptToModel(model, ontology, concept, conceptRootClassMap);
		}
		Map<CandidateConcept, OntClass> conceptClassMap = new HashMap<CandidateConcept, OntClass>(conceptRootClassMap);
		for(CandidateConcept concept : conceptRootClassMap.keySet()) {
			for (CandidateConcept subconcept : concept.getSubConcepts()){
				addNonRootCandidateConceptToModel(model, ontology, subconcept, conceptClassMap);
			}
		}
		return conceptClassMap;
	}
	
	private void addNonRootCandidateConceptToModel(OntModel newModel,
			Model ontology, CandidateConcept concept,
			Map<CandidateConcept, OntClass> conceptClassMap) {
			OntClass newOntClass = createConcept(newModel, concept);
			conceptClassMap.put(concept, newOntClass);
			// now add parents
			for (CandidateConcept parentConcept : concept.getParentConcepts()) {
				if (!conceptClassMap.containsKey(parentConcept)) {
					logger.debug("Adding missing parent concept: " + parentConcept);
					addNonRootCandidateConceptToModel(newModel, ontology, parentConcept, conceptClassMap);
				}
				OntClass superOntClass = conceptClassMap.get(parentConcept);
				newOntClass.addSuperClass(superOntClass);
			}
			for (CandidateConcept subconcept : concept.getSubConcepts()) {
				if (!conceptClassMap.containsKey(subconcept)) {
					addNonRootCandidateConceptToModel(newModel, ontology, subconcept, conceptClassMap);
				}
			}
		
	}

	private void addRootCandidateConceptToModel(OntModel newModel, OntModel ontology,
			CandidateConcept concept, Map<CandidateConcept, OntClass> conceptClassMap) {
		if (concept.getParentConcepts().isEmpty() ) {
			OntClass newConcept = createConcept(newModel, concept);
			OntClass type = ontology.getResource(base+concept.getEntity().getName()).as(OntClass.class);
			newConcept.addSuperClass(type);
			conceptClassMap.put(concept, newConcept);
		} 
	}
	
	private OntClass createConcept(OntModel newModel, CandidateConcept concept) {
		OntClass newConcept = newModel.createClass( base + concept.getNormalizedRepresentation() );
		newConcept.addLabel(newModel.createLiteral(concept.getRepresentation()));
		newConcept.addComment(newModel.createLiteral("Relevance:" + concept.getRelevance()));
		return newConcept;
	}
	

	private OntModel readModel(String path) {
		OntModel model = ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM_TRANS_INF);
		// use the FileManager to find the input file
		InputStream in = FileManager.get().open(path);
		if (in == null) {
			throw new IllegalArgumentException("File: " + path
					+ " not found");
		}

		// read the RDF/XML file
		model.read(in, null, "TURTLE");
		
		return model;

	}
	
	public String listClassesAndProperties(final OntModel oModel) {

		StringBuilder sb = new StringBuilder();
		sb.append("Classes:\n");
		ExtendedIterator<OntClass> oClasses = oModel.listClasses();
		while (oClasses.hasNext()) {
			OntClass oClass = oClasses.next();
			sb.append(String.format("#%s has type %s\n", oClass.getURI(), oClass
					.getRDFType().getLocalName())+"\n");

			ExtendedIterator<OntClass> oSuperClasses = oClass.listSuperClasses();
			while (oSuperClasses.hasNext()) {
				OntClass oSuperClass = oSuperClasses.next();
				sb.append(String.format("\thas superclass #%s",
						oSuperClass.getLocalName()));
			}
			sb.append("\n");
		}

		sb.append("Properties:\n");

		ExtendedIterator<OntProperty> oProperties = oModel.listAllOntProperties();
		while (oProperties.hasNext()) {
			OntProperty oProperty = oProperties.next();
			sb.append(String.format("%s\n", oProperty.getURI()));
		}
		return sb.toString();
	}
	
	/**
	 * Prints the output into the stream in the preferred language.
	 * 
	 * @param stream
	 *            the stream
	 * @param language
	 *            the language (TTL, XML/RDF...)
	 */
	public String modelToString(String language, OntModel model) {
		ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		model.setNsPrefix(prefix, this.base);
		model.write(outputStream, language);
		return outputStream.toString();
	}
	
	public String getOntologyPath() {
		return ontologyPath;
	}

	public void setOntologyPath(String ontologyPath) {
		this.ontologyPath = ontologyPath;
	}

	public String getPrefix() {
		return prefix;
	}

	public void setPrefix(String prefix) {
		this.prefix = prefix;
	}

	public String getBase() {
		return base;
	}

	public void setBase(String base) {
		this.base = base;
	}
	

}
